Windows下使用标准Shell接口遍历文件和文件夹(2)  转自我的CSDN 您所在的位置:网站首页 shell 遍历文件夹 Windows下使用标准Shell接口遍历文件和文件夹(2)  转自我的CSDN

Windows下使用标准Shell接口遍历文件和文件夹(2)  转自我的CSDN

#Windows下使用标准Shell接口遍历文件和文件夹(2)  转自我的CSDN| 来源: 网络整理| 查看: 265

TreeView实现目录树-tree_view的相关方法

使用TreeView实现目录树,其中每个节点都是一个文件夹对象(也可能是虚拟文件夹)。而ListView显示所有的文件对象和子文件夹对象。如下图:

TreeView

如果某个文件夹对象拥有子文件夹,这个文件夹所对应的节点前面会有一个表示可以展开的符号“+”。点击“+”可以展开当前节点,显示这个文件夹对象中的所有子文件夹对象。

实现的时候,我们的TreeView包含一个普通的子窗体和一个TreeView窗体,将这两个窗体作为TreeView使用。TreeView窗体是普通子窗体的子窗体。在使用的时候,如果在主窗体使用我们提供的方法创建TreeView,实际上是在主窗体上创建了一个子窗体,在子窗体中创建一个TreeView。下面要实现的是一个普通的TreeView所必备的一些功能的封装。我称为tree_view。要实现真正的显示本地的命名空间树,或者显示远程主机的命名空间树的时候,可以扩展使用tree_view来实现。

tree_view的实现

下面先定义了一些要使用的回调函数:

1: typedef HRESULT(*pfn_tv_after_create)(struct tree_view* tv); 2: typedef BOOL(*pfn_tv_first_expanding)(struct tree_view* tv, LPTVITEM ptvi); 3: typedef BOOL(*pfn_tv_expanding)(struct tree_view* tv, LPTVITEM ptvi); 4: typedef BOOL(*pfn_tv_collapse)(struct tree_view* tv, LPTVITEM ptvi); 5: typedef HRESULT(*pfn_tv_delitem)(struct tree_view* tv, LPTVITEM ptvi); 6: typedef HRESULT(*pfn_tv_selchanged)(struct tree_view* tv, LPTVITEM ptvi_old, LPTVITEM ptvi_new); 7: typedef HRESULT(*pfn_tv_check_drag_type)(struct tree_view* tv, CLIPFORMAT cfFormat); 8: typedef HRESULT(*pfn_tv_drop)(struct tree_view* tv, CLIPFORMAT cfFormat, STGMEDIUM medium); 9: typedef HRESULT(*pfn_tv_begin_drag) (struct tree_view* tv, LPNMTREEVIEW lpNMTV);

这些回调函数实际上是为了扩展准备的,供给tree_view的使用者实现一些特殊功能所使用的。

下面的结构保存所有的回调函数指针:

1:  5: struct tv_callback 6: { 7: pfn_tv_after_create fn_after_create; 8: pfn_tv_first_expanding fn_first_expanding; 9: pfn_tv_expanding fn_expanding; 10: pfn_tv_collapse fn_collapse; 11: pfn_tv_delitem fn_delitem; 12: pfn_tv_selchanged fn_selchanged; 13: pfn_tv_check_drag_type fn_check_drag_type; 14: pfn_tv_drop fn_drop; 15: pfn_tv_begin_drag fn_begin_drag; 16: };

下面的结构保持着tree_view所需要的所有信息:

1:  5: struct tree_view 6: { 7: struct tv_callback cb; 8: HWND hParent; 9: HWND hWnd; 10: HWND hTree; 11: ATOM atom; 12: BOOL fDragging; 13: LPVOID lpArgs; 14: };

下面这个结构是tree_view中每个节点需要保存的信息:

1:  5: struct tv_node_info 6: { 7: LPTSTR lpszText; 8: int iImage; 9: int iSelImage; 10: int iHasChild; 11: HTREEITEM hParent; 12: HTREEITEM hAfter; 13: LPVOID lpParam; 14: };

下面是提供的对外方法:

1:  12: struct tree_view* tv_create (HWND hParent, LPCTSTR lpszClassName, UINT nID, LPCTSTR lpszTitle, RECT rtPos, 13: struct tv_callback cb, LPVOID lpArgs); 14:  15:  19: void tv_free (struct tree_view* tv); 20:  21:  28: LRESULT CALLBACK tv_WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); 29:  30:  35: HWND tv_get_tree_view (struct tree_view* tv); 36:  37:  43: HTREEITEM tv_insert_node (struct tree_view* tv, struct tv_node_info* tvni); 44:  45:  53: HRESULT tv_DoDragDrop (FORMATETC* pFormatEtc, STGMEDIUM* pStgMedium, UINT nCount, void* pArgs);

下面逐步介绍各个方法

struct tree_view* tv_create (HWND hParent, LPCTSTR lpszClassName, UINT nID, LPCTSTR lpszTitle, RECT rtPos, struct tv_callback cb, LPVOID lpArgs);

这个方法创建一个tree_view对象指针,这个对象指针不再使用的时候需要调用tv_free来释放。

1: struct tree_view* 2: tv_create (HWND hParent, LPCTSTR lpszClassName, UINT nID, LPCTSTR lpszTitle, RECT rtPos, 3: struct tv_callback cb, LPVOID lpArgs) 4: { 5: struct tree_view* tv = NULL; 6: HINSTANCE hInst = NULL; 7: WNDCLASSEX wcex; 8: HWND hWnd; 9:  10: tv = (struct tree_view*) malloc (sizeof (struct tree_view)); 11: if (NULL == tv) 12: { 13: return NULL; 14: } 15: ZeroMemory (tv, sizeof (struct tree_view)); 16:  17: hInst = (HINSTANCE) GetWindowLong (hParent, GWL_HINSTANCE); 18: ZeroMemory (&wcex, sizeof (WNDCLASSEX)); 19: wcex.cbSize = sizeof(WNDCLASSEX); 20:  21: wcex.style = CS_HREDRAW | CS_VREDRAW; 22: wcex.lpfnWndProc = tv_WndProc; 23: wcex.cbClsExtra = 0; 24: wcex.cbWndExtra = 0; 25: wcex.hInstance = hInst; 26: wcex.hIcon = NULL; 27: wcex.hCursor = NULL; 28: wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); 29: wcex.lpszMenuName = NULL; 30: wcex.lpszClassName = lpszClassName; 31: wcex.hIconSm = NULL; 32:  33: tv->atom = RegisterClassEx(&wcex); 34: if (!tv->atom) 35: { 36: free (tv); 37: return NULL; 38: } 39:  40: tv->hParent = hParent; 41: memcpy (&(tv->cb), &cb, sizeof (struct tv_callback)); 42: tv->lpArgs = lpArgs; 43:  44: hWnd = CreateWindowEx (WS_EX_CLIENTEDGE, tv->atom, lpszTitle, WS_CHILD | WS_VISIBLE, 45: rtPos.left, rtPos.top, rtPos.right-rtPos.left, rtPos.bottom-rtPos.top, hParent, NULL, hInst, tv); 46:  47: if (!hWnd) 48: { 49: DWORD dwErr = GetLastError (); 50: free (tv); 51: return NULL; 52: } 53:  54: ShowWindow (hWnd, SW_SHOW); 55: UpdateWindow (hWnd); 56:  57: return tv; 58: }

这个函数创建了tree_view结构对象并注册一个窗体类,最后调用CreateWindowEx创建一个窗体,这个窗体就是TreeView的父窗体,我们会在这个窗体的WM_CREATE消息中创建TreeView。用户传递的参数pArgs保持在tree_view的pArgs成员中,这是用户数据。特别注意的是在调用CreateWindowEx时,最后一个参数传递了tree_view的指针,这个指针在tree_view的父窗体(以后称为当前窗体)接收到WM_CREATE消息时放在CREATESTRUCT结构的lParam成员中。

void tv_free (struct tree_view* tv);

这个方法销毁创建的当前窗体和tree_view对象。

1: void 2: tv_free (struct tree_view* tv) 3: { 4: HINSTANCE hInst = NULL; 5: if (tv) 6: { 7: if (tv->hWnd) 8: { 9: DestroyWindow (tv->hWnd); 10: } 11: if (tv->atom) 12: { 13: hInst = (HINSTANCE) GetWindowLong (tv->hParent, GWL_HINSTANCE); 14: UnregisterClass ((LPCTSTR)tv->atom, hInst); 15: } 16: free (tv); 17: } 18: }

下面是当前窗体的消息响应函数,我们先给出代码,然后进行详细介绍:

1: LRESULT CALLBACK 2: tv_WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 3: { 4: switch (message) 5: { 6: case WM_CREATE: 7: { 8: CREATESTRUCT* cs; 9: cs = (CREATESTRUCT*) lParam; 10: return _tv_on_create (hWnd, cs); 11: } 12: break; 13: case WM_NOTIFY: 14: { 15: struct tree_view* tv = (struct tree_view*) GetWindowLong (hWnd, GWL_USERDATA); 16: if (NULL == tv) 17: { 18: return TRUE; // 有错误,不能展开 19: } 20: switch (((LPNMHDR) lParam)->code) 21: { 22: case TVN_ITEMEXPANDING: 23: { 24: LPNMTREEVIEW lpNMTV = (LPNMTREEVIEW) lParam; 25: switch (lpNMTV->action) 26: { 27: case TVE_EXPAND: 28: { 29: if (!(lpNMTV->itemNew.state & TVIS_EXPANDEDONCE)) 30: { 31: return tv->cb.fn_first_expanding (tv, &(lpNMTV->itemNew)); 32: } 33: else 34: { 35: return tv->cb.fn_expanding (tv, &(lpNMTV->itemNew)); 36: } 37: } 38: break; 39: case TVE_COLLAPSE: 40: { 41: return tv->cb.fn_collapse (tv, &(lpNMTV->itemNew)); 42: } 43: break; 44: } 45: } 46: break; 47: case TVN_DELETEITEM: 48: { 49: LPNMTREEVIEW lpNMTV = (LPNMTREEVIEW) lParam; 50: return tv->cb.fn_delitem (tv, &(lpNMTV->itemOld)); 51: } 52: break; 53: case TVN_SELCHANGED: 54: { 55: LPNMTREEVIEW lpNMTV = (LPNMTREEVIEW) lParam; 56: return tv->cb.fn_selchanged (tv, &(lpNMTV->itemOld), &(lpNMTV->itemNew)); 57: } 58: break; 59: case TVN_BEGINDRAG: 60: { 61: LPNMTREEVIEW* lpNMTV = (LPNMTREEVIEW) lParam; 62: return _tv_on_begin_drag (tv, lpNMTV); 63: } 64: break; 65: } 66: } 67: break; 68: case WM_MOUSEMOVE: 69: { 70: struct tree_view* tv = (struct tree_view*) GetWindowLong (hWnd, GWL_USERDATA); 71: return _tv_on_mouse_move_drag (tv, wParam, lParam); 72: } 73: break; 74: case WM_LBUTTONUP: 75: { 76: struct tree_view* tv = (struct tree_view*) GetWindowLong (hWnd, GWL_USERDATA); 77: return _tv_on_lbtn_up_drag (tv, wParam, lParam); 78: } 79: break; 80: case TV_M_CHANGEFOCUS: 81: { 82: struct tree_view* tv = (struct tree_view*) GetWindowLong (hWnd, GWL_USERDATA); 83: TreeView_SelectDropTarget(tv->hTree, NULL); 84: TreeView_SelectItem(tv->hTree, (HTREEITEM)lParam) ; 85: } 86: break; 87: default: 88: return DefWindowProc(hWnd, message, wParam, lParam); 89: } 90: return S_OK; 91: } WM_CREATE 消息

当调用CreateWindowEx创建当前窗体时,我们在最后的参数中传入了tree_view对象的指针。这里通过CREATESTRUCT结构的lParam成员获得了这个指针,并调用了我们自己的内部函数_tv_on_create:

1: static LRESULT 2: _tv_on_create (HWND hWnd, CREATESTRUCT* cs) 3: { 4: HRESULT hr; 5: RECT rt; 6: HWND hTree = NULL; 7: struct tree_view* tv = NULL; 8: IDropTarget* pDT = NULL; 9: struct idt_lpVtbl idtVtbl; 10:  11: GetClientRect (hWnd, &rt); 12:  13: // 创建TreeView 14: hTree = CreateWindow (WC_TREEVIEW, 15: "", 16: WS_CHILD | LVS_REPORT | WS_VISIBLE|TVS_HASLINES|TVS_HASBUTTONS|TVS_LINESATROOT|TVS_SHOWSELALWAYS , 17: 0, 18: 0, 19: rt.right-rt.left, 20: rt.bottom-rt.top, 21: hWnd, 22: (HMENU) ID_LOCAL_DIR_TREE, 23: (HINSTANCE) GetWindowLong (hWnd, GWL_HINSTANCE), 24: NULL); 25: if (NULL == hTree) 26: { 27: return -1; 28: } 29:  30: tv = (struct tree_view*) cs->lpCreateParams; 31: tv->hTree = hTree; 32: tv->hWnd = hWnd; 33:  34: // 设置窗体参数 35: SetWindowLong (hWnd, GWL_USERDATA, (LONG) tv); 36:  37:  51: if (tv->cb.fn_after_create) 52: { 53: return tv->cb.fn_after_create (tv); 54: } 55: return S_OK; 56: }

与拖拽有关的方法我都注释掉了,因为在测试拖拽的时候遇到了一些问题。这个方法调用CreateWindow创建了一个TreeView子窗体,并将tree_view对象指针保存在窗体的用户数据中,以便以后处理其它消息的时候使用。最后我们调用了用户的回调函数。

下面我们看一下WM_NOTIFY消息,这个消息用于当前窗体处理TreeView的事件。我们处理了TVN_ITEMEXPANDING、TVN_DELETEITEM、TVN_SELCHANGED和TVN_BEGINDRAG事件。

TVN_ITEMEXPANDING 事件

当TreeView中的某个节点被展开或者收缩的时候,这个事件被激发。这时,WM_NOTIFY消息的lParam参数是NMTREEVIEW结构的指针。NMTREEVIEW结构中的action成员表示当前节点是被展(TVE_EXPAND)开还是被收缩(TVE_COLLAPSE)。NMTREEVIEW结构中的itemNew表示发出事件的当前节点。如果是被展开,当前节点的state成员标识着当前节点的状态,当某个节点第一次被展开时,state的TVIS_EXPANDEDONCE标志没有被置位,第一次展开完成后,state的TVIS_EXPANDEDONCE标志被置位。所以我们通过探测TVIS_EXPANDEDONCE标志是否被置位,来判断当前节点是否是第一次被展开。通常在某个节点第一次被展开时,准备相关的数据(如创建并填充所有的子节点);而不是第一次被展开时,我们可以什么都不做。无论是被展开还是被收缩,具体的操作我们都是调用回调函数,让用户实现。

TVN_DELETEITEM 事件

当TreeView中某个节点被删除时,这个事件被激发。这时,WM_NOTIFY消息的lParam参数是NMTREEVIEW结构的指针。NMTREEVIEW结构中的itemOld成员表示要被删除的节点。在这里我们调用用户参数,因为节点是用户创建的,用户可能在节点中存储了用户自定义信息,在这里用户可以释放用户自定义信息所使用的资源。

TVN_SELCHANGED 事件

当TreeView中某个节点被选中时,这个事件被激发。这时,WM_NOTIFY消息的lParam参数是NMTREEVIEW结构的指针。其中itemOld表示以前被选中的节点,itemNew表示后来被选中的新节点。这里调用用户的回调函数,让用户处理,如控制ListView显示当前选中的新节点的文件对象和子文件夹对象等。

TVN_BEGINDRAG 事件

当TreeView中某个节点开始被拖拽时,这个事件被激发。这时,WM_NOTIFY消息的lParam参数是NMTREEVIEW结构的指针。NMTREEVIEW结构中的itemNew表示被拖拽的当前节点。这里调用_tv_on_begin_drag函数:

1: static LRESULT 2: _tv_on_begin_drag (struct tree_view* tv, LPNMTREEVIEW lpNMTV) 3: { 4:  28: return S_OK ; 29: }

这个函数被注释的部分先调用了用户的回调函数,然后设置拖拽的图标信息。

WM_MOUSEMOVE 消息

这个消息中调用了_tv_on_mouse_move_drag来处理拖拽过程中的图标显示:

1: static BOOL 2: _tv_on_mouse_move_drag (struct tree_view* tv, WPARAM wParam, LPARAM lParam) 3: { 4: POINT pnt; 5: HTREEITEM hItem = NULL; 6: TVHITTESTINFO tv_ht; 7:  8: pnt.x = GET_X_LPARAM(lParam); 9: pnt.y = GET_Y_LPARAM(lParam); 10:  11: if (tv->fDragging) 12: { 13: //unlock window and allow updates to occur 14: ImageList_DragLeave(NULL) ; 15: ClientToScreen(tv->hWnd, &pnt) ; 16: //check with the tree control to see if we are on an item 17: ZeroMemory(&tv_ht, sizeof(TVHITTESTINFO)); 18: tv_ht.flags = TVHT_ONITEM; 19: tv_ht.pt.x = pnt.x; 20: tv_ht.pt.y = pnt.y; 21: ScreenToClient(tv->hTree, &(tv_ht.pt)); 22: hItem = (HTREEITEM)SendMessage(tv->hTree, TVM_HITTEST, 0, (LPARAM)&tv_ht); 23:  24: if (hItem) 25: { 26: //if we had a hit, then drop highlite the item 27: TreeView_SelectItem (tv->hTree, hItem); 28: } 29:  30: //paint the image in the new location 31: ImageList_DragMove(pnt.x,pnt.y); 32: //lock the screen again 33: ImageList_DragEnter(NULL, pnt.x, pnt.y); 34: } 35: return TRUE; 36: }

在这个处理过程中,不断的探测鼠标拖拽的对象覆盖了哪个节点,使被覆盖的节点处于被选中状态。

WM_LBUTTONUP 消息

这个消息表示拖拽结束,调用_tv_on_lbtn_up_drag停止拖拽图标的显示:

1: static BOOL 2: _tv_on_lbtn_up_drag (struct tree_view* tv, WPARAM wParam, LPARAM lParam) 3: { 4: HTREEITEM hItem = NULL; 5: TVHITTESTINFO tv_ht; 6: TVITEM tvi; 7: ZeroMemory(&tvi, sizeof(TVITEM)); 8: ZeroMemory(&tv_ht, sizeof(TVHITTESTINFO)); 9:  10: if (tv->fDragging) 11: { 12: ImageList_DragLeave(NULL); 13: ImageList_EndDrag(); 14: ReleaseCapture(); 15: //determin if we let up on an item 16: GetCursorPos(&(tv_ht.pt)); 17: ScreenToClient(tv->hTree, &(tv_ht.pt)); 18: tv_ht.flags = TVHT_ONITEM; 19: hItem = (HTREEITEM)SendMessage(tv->hTree, TVM_HITTEST, 0, (LPARAM)&tv_ht); 20: ShowCursor(TRUE); 21: tv->fDragging = FALSE; 22: if (hItem) 23: { 24:  25: PostMessage(tv->hWnd, TV_M_CHANGEFOCUS, (WPARAM)0, (LPARAM)hItem); 26: } 27: } 28: return TRUE; 29: }

在这个消息中调用PostMessage发送了一个自定义的TV_M_CHANGEFOCUS异步消息,在这个消息中,我们调用TreeView_SelectDropTarget来选中最终拖拽的目标节点。

HWND tv_get_tree_view (struct tree_view* tv);

用于获得tree_view中的TreeView窗体句柄。

1: HWND 2: tv_get_tree_view (struct tree_view* tv) 3: { 4: if (tv) 5: { 6: return tv->hTree; 7: } 8: return NULL; 9: } HTREEITEM tv_insert_node (struct tree_view* tv, struct tv_node_info* tvni);

用于插入一个新节点:

1: HTREEITEM 2: tv_insert_node (struct tree_view* tv, struct tv_node_info* tvni) 3: { 4: TVINSERTSTRUCT tvins; 5: TVITEM tvi; 6: if (NULL == tv || NULL == tvni) 7: { 8: return NULL; 9: } 10:  11: ZeroMemory (&tvins, sizeof (TVINSERTSTRUCT)); 12: ZeroMemory (&tvi, sizeof (TVITEM)); 13:  14: tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM; 15: if (tvni->iHasChild) 16: { 17: tvi.mask |= TVIF_CHILDREN; 18: tvi.cChildren = 1; 19: } 20: tvi.cchTextMax = MAX_PATH; 21: tvi.pszText = tvni->lpszText; 22: tvi.iImage = tvni->iImage; 23: tvi.iSelectedImage = tvni->iSelImage; 24: tvi.lParam = (LPARAM) tvni->lpParam; 25: tvins.item = tvi; 26: tvins.hInsertAfter = tvni->hAfter; 27: tvins.hParent = tvni->hParent; 28: return TreeView_InsertItem (tv->hTree, &tvins); 29: }

在这个函数中,调用TreeView_InsertIterm来插入节点,并且设置了节点的文字、图片、被选中时的图片以及用户参数。

HRESULT tv_DoDragDrop (FORMATETC* pFormatEtc, STGMEDIUM* pStgMedium, UINT nCount, void* pArgs);

这个函数用于拖拽某个节点后,构造拖拽信息,并调用DoDragDrop函数进行拖拽后的处理。其中需要实现IDataObject和IDropSource这两个系统接口。如果想实现将某个节点表示的文件夹拖拽到支持拖拽的目标后,将这个文件夹拷贝到目标对象,那么必须调用这个函数。它可以支持将节点拖拽到Explorer。以后再详细介绍吧:

1: HRESULT tv_DoDragDrop (FORMATETC* pFormatEtc, STGMEDIUM* pStgMedium, UINT nCount, void* pArgs) 2: { 3: 4: IDataObject* pDO = NULL; 5: IDropSource* pDS = NULL; 6: struct ido_lpVtbl ido_vtbl; 7: struct ids_lpVtbl ids_vtbl; 8: DWORD dwOKEffect; 9:  10: ZeroMemory (&ido_vtbl, sizeof (struct ido_lpVtbl)); 11: ZeroMemory (&ids_vtbl, sizeof (struct ids_lpVtbl)); 12:  13: ido_vtbl.DAdvise = _tv_DAdvise; 14: ido_vtbl.DUnadvise = _tv_DUnadvise; 15: ido_vtbl.EnumDAdvise = _tv_EnumDAdvise; 16: ido_vtbl.EnumFormatEtc = _tv_EnumFormatEtc; 17: ido_vtbl.GetCanonicalFormatEtc = _tv_GetCanonicalFormatEtc; 18: ido_vtbl.GetData = _tv_GetData; 19: ido_vtbl.GetDataHere = _tv_GetDataHere; 20: ido_vtbl.QueryGetData = _tv_QueryGetData; 21: ido_vtbl.SetData = _tv_SetData; 22:  23: pDO = ido_create (&ido_vtbl, pFormatEtc, pStgMedium, nCount, pArgs); 24: if (NULL == pDO) 25: { 26: return S_FALSE; 27: } 28:  29: ids_vtbl.GiveFeedback = _tv_GiveFeedback; 30: ids_vtbl.QueryContinueDrag = _tv_QueryContinueDrag; 31:  32: pDS = ids_create (&ids_vtbl, pArgs); 33: if (NULL == pDS) 34: { 35: pDO->lpVtbl->Release (pDO); 36: return S_FALSE; 37: } 38:  39: ZeroMemory (&dwOKEffect, sizeof (DWORD)); 40:  41: return DoDragDrop (pDO, pDS, DROPEFFECT_COPY, &dwOKEffect); 42: }


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有